/*
 * timer.c
 *
 *  Created on: Apr 4, 2021
 *      Author: gewenbin
 */
#define  __SYLIXOS_KERNEL
#include <SylixOS.h>
#include <linux/compat.h>
#include "timer.h"

#define TIMER_BASE     0x02050000
#define TIMER_IRQ_EN   0x00
#define TIMER_IRQ_STA  0x04
#define TIMER0_CTRL    0x10
#define TIMER0_INTV    0x14
#define TIMER0_CUR     0x18
#define TIMER1_CTRL    0x20
#define TIMER1_INTV    0x24
#define TIMER1_CUR     0x28

static UINT32  u32FullCnt;
static UINT64  u64NsecPerCnt7;

VOID  timerStart (INT32  iNum, UINT32  uiHZ)
{
    UINT32  uiCount;
    UINT32  uiIntvOffset;
    UINT32  uiCtrlOffset;

    if (0 == iNum) {
        uiIntvOffset = TIMER0_INTV;
        uiCtrlOffset = TIMER0_CTRL;
    } else if (1 == iNum) {
        uiIntvOffset = TIMER1_INTV;
        uiCtrlOffset = TIMER1_CTRL;
    } else {
        return ;
    }

    uiCount  = TIMER_FREQ;
    uiCount /= uiHZ;

    if (0 == iNum) {
        u32FullCnt = uiCount;
        u64NsecPerCnt7 = ((1000 * 1000 * 1000 / LW_TICK_HZ) << 7) / u32FullCnt;
    }

    writel(uiCount, TIMER_BASE + uiIntvOffset);
    writel(BIT(1) | BIT(2) | BIT(4), TIMER_BASE + uiCtrlOffset);
    while ((readl(TIMER_BASE + uiCtrlOffset) >> 1) & 0x01);
    writel(readl(TIMER_BASE + uiCtrlOffset) | BIT(0), TIMER_BASE + uiCtrlOffset);
    writel(readl(TIMER_BASE + TIMER_IRQ_EN) | BIT(iNum), TIMER_BASE + TIMER_IRQ_EN);
}

VOID  timerIntClear (INT32  iNum)
{
    if ((0 != iNum) && (1 != iNum))
        return ;

    writel(readl(TIMER_BASE + TIMER_IRQ_STA) | BIT(iNum), TIMER_BASE + TIMER_IRQ_STA);
}

BOOL  timerIsIntPending (INT32  iNum)
{
    if ((0 != iNum) && (1 != iNum))
        return  FALSE;

    return  (readl(TIMER_BASE + TIMER_IRQ_STA) & BIT(iNum)) ? TRUE : FALSE;
}

static UINT32  timerCurGet (INT32  iNum)
{
    if ((0 != iNum) && (1 != iNum))
        return 0;

    return  readl(TIMER_BASE + (TIMER0_CUR + iNum * 0x10));
}

VOID  timerTickHighResolution (struct timespec *ptv)
{
    register UINT32  u32DoCnt;

    /*
     * work out how many counts have gone since last timer interrupt
     */
    u32DoCnt = u32FullCnt - timerCurGet(0);

    /*
     * check to see if there is an interrupt pending
     */
    if (timerIsIntPending(0)) {
        /*
         * re-read the timer, and try and fix up for the missed
         * interrupt. Note, the interrupt may go off before the
         * timer has re-loaded from wrapping.
         */
        u32DoCnt = u32FullCnt - timerCurGet(0);

        if (u32DoCnt != u32FullCnt) {
            u32DoCnt += u32FullCnt;
        }
    }

    ptv->tv_nsec += (LONG)((u64NsecPerCnt7 * u32DoCnt) >> 7);
    if (ptv->tv_nsec >= 1000000000) {
        ptv->tv_nsec -= 1000000000;
        ptv->tv_sec++;
    }
}
